package blue.starter.internal.httpclient.core;

import blue.starter.httpclient.HttpClient;
import blue.starter.httpclient.StringResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.task.TaskExecutor;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.InetSocketAddress;
import java.net.ProxySelector;
import java.net.URI;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;

/**
 * @author Jin Zheng
 * @date 2020-07-08
 */
public class DefaultHttpClient implements HttpClient, InitializingBean
{
	private static Logger logger = LoggerFactory.getLogger(DefaultHttpClient.class);

	private String name;
	private String baseUrl;
	private int timeout;
	private String username;
	private String password;
	private String proxy;
	private Map<String, String> defaultHeaders;
	private TaskExecutor taskExecutor;

	private java.net.http.HttpClient httpClient;

	public DefaultHttpClient()
	{
	}

	@Override
	public StringResponse requestSync(String uri, String method, String body, Map<String, String> header)
	{
		HttpRequest.BodyPublisher publisher = this.publisher(body);
		HttpRequest.Builder builder = this.builder(uri, method, header, publisher);

		try
		{
			HttpResponse<String> response = httpClient.send(builder.build(), HttpResponse.BodyHandlers.ofString());
			return new DefaultStringResponse(response);
		}
		catch (Exception e)
		{
			this.handleException(e);
			return null;
		}
	}

	@Override
	public CompletableFuture<StringResponse> requestAsync(String uri, String method, String body, Map<String, String> header)
	{
		HttpRequest.BodyPublisher publisher = this.publisher(body);
		HttpRequest.Builder builder = this.builder(uri, method, header, publisher);
		CompletableFuture<HttpResponse<String>> future = httpClient.sendAsync(builder.build(), HttpResponse.BodyHandlers.ofString());
		return future.thenApply(s -> new DefaultStringResponse(s));
	}


	private HttpRequest.Builder builder(String uri, String method, Map<String, String> header, HttpRequest.BodyPublisher publisher)
	{
		String url = (baseUrl != null && !baseUrl.isEmpty()) ? baseUrl + uri : uri;
		HttpRequest.Builder builder = HttpRequest.newBuilder(URI.create(url));
		Map<String, String> map = new HashMap<>();
		if (defaultHeaders != null && !defaultHeaders.isEmpty())
		{
			map.putAll(defaultHeaders);
		}
		if (header != null && !header.isEmpty())
		{
			map.putAll(header);
		}
		for (Map.Entry<String, String> entry : map.entrySet())
		{
			builder.header(entry.getKey(), entry.getValue());
		}
		builder.method(method, publisher);
		builder.timeout(Duration.ofMillis(timeout));
		return builder;
	}

	private HttpRequest.BodyPublisher publisher(String body)
	{
		if (body != null && !body.isEmpty())
			return HttpRequest.BodyPublishers.ofString(body);

		return HttpRequest.BodyPublishers.noBody();
	}

	private void handleException(Exception cause)
	{
		logger.error("Error, ", cause);
		if (cause instanceof IOException)
		{
			throw new UncheckedIOException((IOException) cause);
		}
	}

	@Override
	public void afterPropertiesSet() throws Exception
	{
		java.net.http.HttpClient.Builder builder = java.net.http.HttpClient.newBuilder();
		if (timeout > 0)
		{
			builder.connectTimeout(Duration.ofMillis(timeout));
		}
		if (proxy != null && !proxy.isEmpty())
		{
			String[] proxys = proxy.split(":");
			if (proxys.length != 2)
				throw new IllegalArgumentException("Invalid proxy: " + proxy);

			builder.proxy(ProxySelector.of(new InetSocketAddress(proxys[0], Integer.parseInt(proxys[1]))));
		}
		if (taskExecutor != null)
		{
			builder.executor(taskExecutor);
		}
		if (username != null && !username.isEmpty() && password != null && !password.isEmpty())
		{
			if (defaultHeaders == null)
			{
				defaultHeaders = new HashMap<>();
			}
			defaultHeaders.put("Authorization", this.authorization());
		}
		this.httpClient = builder.build();
	}

	private String authorization()
	{
		String auth = username + ":" + password;
		return "Basic " + Base64.getEncoder().encodeToString(auth.getBytes());
	}

	@Override
	public String getName()
	{
		return name;
	}

	public void setName(String name)
	{
		this.name = name;
	}

	@Override
	public String getBaseUrl()
	{
		return baseUrl;
	}

	public void setBaseUrl(String baseUrl)
	{
		this.baseUrl = baseUrl;
	}

	@Override
	public int getTimeout()
	{
		return timeout;
	}

	public void setTimeout(int timeout)
	{
		this.timeout = timeout;
	}

	@Override
	public String getProxy()
	{
		return proxy;
	}

	public void setProxy(String proxy)
	{
		this.proxy = proxy;
	}

	public Map<String, String> getDefaultHeaders()
	{
		return defaultHeaders;
	}

	@Override
	public Map<String, String> getHeaders()
	{
		return Map.copyOf(defaultHeaders);
	}

	@Override
	public String getUsername()
	{
		return username;
	}

	public void setUsername(String username)
	{
		this.username = username;
	}

	@Override
	public String getPassword()
	{
		return password;
	}

	public void setPassword(String password)
	{
		this.password = password;
	}

	public void setDefaultHeaders(Map<String, String> defaultHeaders)
	{
		this.defaultHeaders = defaultHeaders;
	}

	public TaskExecutor getTaskExecutor()
	{
		return taskExecutor;
	}

	public void setTaskExecutor(TaskExecutor taskExecutor)
	{
		this.taskExecutor = taskExecutor;
	}

}
